﻿using System;
using System.Collections.Generic;

namespace Ns
{
    /// <summary>
    /// Represents a week period. This class cannot be inherited.
    /// </summary>
    public sealed class WeekPeriod : Interval<Week?>, IFormattable
    {
        #region Properties
        /// <summary>
        /// Gets the start week.
        /// </summary>
        /// <value>The start week.</value>
        public Week? Start
        {
            get { return LowerBound.Value; }
        }

        /// <summary>
        /// Gets the end date and time.
        /// </summary>
        /// <value>The end date and time.</value>
        public Week? End
        {
            get { return UpperBound.Value; }
        }
        #endregion

        #region Constructors
        /// <summary>
        /// Initializes a new instance of the <see cref="WeekPeriod"/> class.
        /// </summary>
        /// <param name="lowerDirection">The lower direction.</param>
        /// <param name="lowerValue">The lower value.</param>
        /// <param name="upperValue">The upper value.</param>
        /// <param name="upperDirection">The upper direction.</param>
        public WeekPeriod(Week? lowerValue, Week? upperValue)
            : base(new Bound<Week?>(BoundType.Lower, BoundDirection.Closed, lowerValue),
                   new Bound<Week?>(BoundType.Upper, BoundDirection.Closed, upperValue)) { }
        #endregion

		#region Static
		public static WeekPeriod FromPeriod(Period period)
        {
            return FromPeriod(period, Week.NewIsoWeek);
        }

        public static WeekPeriod FromPeriod(Period period, Func<DateTime, Week> weekBuilder)
        {
			Week? firstWeek = period.LowerBound.Value.HasValue ? (Week?)weekBuilder(FirstIncludedDay(period)) : null;
			Week? lastWeek = period.UpperBound.Value.HasValue ? (Week?)weekBuilder(LastIncludedDay(period)) : null;

            return new WeekPeriod(firstWeek, lastWeek);
        }

		///<summary>
		/// Get the first included day of a period.
		///</summary>
		///<param name="period">The period.</param>
		///<returns></returns>
		private static DateTime FirstIncludedDay(Period period)
		{
			if (period.LowerBound.Direction == BoundDirection.Closed)
				return period.LowerBound.Value.GetValueOrDefault().LocalDateTime.Date;
			else
				return period.LowerBound.Value.GetValueOrDefault().LocalDateTime.AddTicks(1).Date;
		}

		///<summary>
		/// Get the last included day of a period.
		///</summary>
		///<param name="period">The period.</param>
		///<returns></returns>
		private static DateTime LastIncludedDay(Period period)
		{
			if (period.UpperBound.Direction == BoundDirection.Closed)
				return period.UpperBound.Value.GetValueOrDefault().LocalDateTime.Date;
			else
				return period.UpperBound.Value.GetValueOrDefault().LocalDateTime.AddTicks(-1).Date;
		}
		#endregion

		public IEnumerable<Week> GetWeeks()
        {
            if (!Start.HasValue || !End.HasValue)
                return null;

            var weeks = new List<Week>();

            Week week = Start.Value;
            if (LowerBound.Direction == BoundDirection.Opened)
                week += 1;

            while (week < End)
            {
                weeks.Add(week);
                week += 1;
            }

            if (UpperBound.Direction == BoundDirection.Closed)
                weeks.Add(week);

            return weeks;
        }

		#region IFormattable Members
		/// <summary>
		/// Converts the value of the current <see cref="WeekPeriod"/> object to its equivalent string representation using the specified format information.
		/// </summary>
		/// <param name="format">A format string.</param>
		/// <returns>
		/// A string representation of the value of the current <see cref="WeekPeriod"/> object, as specified by <paramref name="format"/>.
		/// </returns>
		public string ToString(string format)
		{
			return ToString(format, null);
		}

		/// <summary>
		/// Converts the value of the current <see cref="WeekPeriod"/> object to its equivalent string representation using the specified culture-specific format information.
		/// </summary>
		/// <param name="formatProvider">An <see cref="IFormatProvider"/> object that supplies culture-specific formatting information.</param>
		/// <returns>
		/// A string representation of the value of the current <see cref="WeekPeriod"/> object, as specified by <paramref name="formatProvider"/>.
		/// </returns>
		public string ToString(IFormatProvider formatProvider)
		{
			return ToString(null, null);
		}

		/// <summary>
		/// Converts the value of the current <see cref="WeekPeriod"/> object to its equivalent string representation using the specified format and culture-specific format information.
		/// </summary>
		/// <param name="format">A format string.</param>
		/// <param name="formatProvider">An <see cref="IFormatProvider"/> object that supplies culture-specific formatting information.</param>
		/// <returns>
		/// A string representation of the value of the current <see cref="WeekPeriod"/> object, as specified by <paramref name="format"/> and <paramref name="formatProvider"/>.
		/// </returns>
		public string ToString(string format, IFormatProvider formatProvider)
		{
			return IsEmpty
				? Bound.EmptySet
				: IsSingleton
					? LowerBound.Value.Value.ToString(format, formatProvider)
					: string.Format("{0}{1} ; {2}{3}"
					, LowerBound.Direction == BoundDirection.Closed ? "[" : "]"
					, object.Equals(LowerBound.Value, default(object)) ? Bound.Infinity : LowerBound.Value.Value.ToString(format, formatProvider)
					, object.Equals(UpperBound.Value, default(object)) ? Bound.Infinity : UpperBound.Value.Value.ToString(format, formatProvider)
					, UpperBound.Direction == BoundDirection.Closed ? "]" : "[");
		}
		#endregion
    }
}